package org.daochong.lang;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SQLBuilder {

	private static SQLBuilder instance;
	public static String DEFAULT_PATH = "/sqls";
	private Map<Resource, Long> resourcesTimes = new HashMap<Resource, Long>();

	public static SQLBuilder getInstance() {
		if (instance == null) {
			instance = new SQLBuilder();
			try {
				List<Resource> rs = Resource.getResourcesByFolder(DEFAULT_PATH);
				rs = Resource.getResources(DEFAULT_PATH + "/*.properties");
				if (rs != null) {
					List<String> list = new ArrayList<String>();
					for (Resource r : rs) {
						if (list.contains(r.getFileName())) {
							continue;
						}
						list.add(r.getFileName());
						instance.loadResource(r);
					}
				}
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}
		return instance;
	}

	public static void load(File file) {
		if (instance == null) {
			instance = new SQLBuilder();
		}
		if (file == null || !file.exists()) {
			return;
		}
		try {
			if (file.isFile()) {
				if (file.getName().endsWith(".properties")) {
					instance.load(new FileInputStream(file));
				}
			} else {
				for (File f : file.listFiles()) {
					load(f);
				}
			}
		} catch (Throwable e) {

		}
	}

	public static void load(Resource resource) {
		if (instance == null) {
			instance = new SQLBuilder();
		}
		if (resource == null || !resource.getSourceFile().exists()) {
			return;
		}
		instance.loadResource(resource);
	}

	public static void setInstance(SQLBuilder instance) {
		SQLBuilder.instance = instance;
	}

	private Properties sqls;

	public SQLBuilder() {
		sqls = new Properties();
	}

	public SQLBuilder(InputStream in) {
		sqls = new Properties();
		load(in);
	}

	public SQLBuilder(List<InputStream> ins) {
		sqls = new Properties();
		load(ins);
	}

	public void load(InputStream in) {
		Properties p = new Properties();
		p.load(in);
		sqls.addAll(p);
	}

	public void loadResource(Resource resource) {
		Properties p = new Properties();
		try {
			p.load(resource.getInputStream());
			sqls.addAll(p);
			this.resourcesTimes.put(resource, resource.getSourceFile().lastModified());
		} catch (IOException e) {

		}

	}

	public void load(List<InputStream> ins) {
		if (ins != null && ins.size() > 0) {
			for (InputStream in : ins) {
				Properties p = new Properties();
				p.load(in);
				sqls.addAll(p);
			}
		}
	}

	private void check() {
		if (this.resourcesTimes.size() > 0) {
			for (Resource resource : this.resourcesTimes.keySet()) {
				if (resource.lastModified() > this.resourcesTimes.get(resource)) {
					this.loadResource(resource);
				}
			}
		}
	}

	public String getSql(String id) {
		check();
		String sql = this.sqls.getProperty(id);
		if (sql == null)
			sql = this.sqls.getProperty(BeanUtil.toDatabaseCol(id).toLowerCase().replaceAll("_", "."));
		if (sql == null)
			throw new RuntimeException("SQL " + id + " not exists");
		return sql;
	}

	@SuppressWarnings("unchecked")
	public SQLQuery getSql(String id, Map<String, Object> params) {
		if (id == null) {
			throw new RuntimeException("SQL " + id + " is null");
		}
		check();
		String sql = getSql(id);
		if (sql == null) {
			for (String key : params.keySet()) {
				if (id.indexOf("#" + key + "#") == -1) {
					throw new RuntimeException("SQL " + id + " not exists");
				}
			}
			sql = id;
		}
		SQLQuery query = new SQLQuery();
		Pattern p = Pattern.compile("@([^@]+)@");
		Matcher matcher = p.matcher(sql);
		boolean result1 = matcher.find();
		int count = 0;
		while (result1) {
			StringBuffer sb = new StringBuffer();
			boolean find = false;
			while (result1) {
				String key = matcher.group();
				key = key.substring(1, key.length() - 1);
				String s = getSql(key);
				if (s != null) {
					if (s.indexOf("@" + key + "@") >= 0) {
						throw new RuntimeException("SQL [" + key + "] refer self");
					}
					matcher.appendReplacement(sb, s);
					find = true;
				}
				result1 = matcher.find();
				count++;
			}
			matcher.appendTail(sb);
			sql = sb.toString();
			if (count >= 100) {
				throw new RuntimeException("SQL nested > 100");
			}
			if (find) {
				matcher = p.matcher(sql);
				result1 = matcher.find();
			}
		}
		p = Pattern.compile("#([\\w]+)#");
		matcher = p.matcher(sql);
		StringBuffer sb = new StringBuffer();
		result1 = matcher.find();
		List<Object> ps = new ArrayList<Object>();

		if (result1 && params == null) {
			throw new RuntimeException("params is null");
		}
		while (result1) {
			String key = matcher.group();
			key = key.substring(1, key.length() - 1);
			if (!params.containsKey(key))
				throw new RuntimeException("Param " + key + " not exists");
			Object value = params.get(key);
			if (value instanceof List) {
				List<Object> list = (List<Object>) value;
				if (list == null || list.size() == 0) {
					ps.add(null);
					matcher.appendReplacement(sb, "?");
				} else {
					String str = "";
					for (Object o : list) {
						str += "?,";
						ps.add(o);
					}
					str = str.substring(0, str.length() - 1);
					matcher.appendReplacement(sb, str);
				}
			} else {
				ps.add(value);
				matcher.appendReplacement(sb, "?");
			}
			result1 = matcher.find();
		}
		matcher.appendTail(sb);
		query.setSql(sb.toString());
		query.setParams(ps);
		return query;
	}

	public List<String> getSqlParams(String id) {
		String sql = getSql(id);
		Pattern p = Pattern.compile("#([\\w]+)#");
		Matcher matcher = p.matcher(sql);
		boolean result1 = matcher.find();
		List<String> keys = new ArrayList<String>();
		while (result1) {
			String key = matcher.group();
			key = key.substring(1, key.length() - 1);
			if (!keys.contains(key)) {
				keys.add(key);
			}
			result1 = matcher.find();
		}
		return keys;
	}

	public Set<String> keys() {
		return this.sqls.keys();
	}

	public Set<String> keys(String prefix) {
		Set<String> keys = new HashSet<String>();
		for (String key : keys()) {
			if (key.startsWith(prefix)) {
				keys.add(key);
			}
		}
		return keys;
	}

	public boolean exists(String sqlId) {
		return this.sqls.getProperty(sqlId) != null
				|| this.sqls.getProperty(BeanUtil.toDatabaseCol(sqlId).toLowerCase().replaceAll("_", ".")) != null;
	}

}
